import alsaaudio
import struct
from math import sin, pi, ceil
from threading import Thread, Event, Lock
import time
import logging


class Morse(Thread):
    __morse_table = {'a':'.-',     'b':'-...',   'c':'-.-.',  ' d':'-..',
                     'e':'.',      'f':'..-.',   'g':'--.',    'h':'....',
                     'i':'..',     'j':'.---',   'k':'-.-',    'l':'.-..',
                     'm':'--',     'n':'-.',     'o':'---',    'p':'.--.',
                     'q':'--.-',   'r':'.-.',    's':'...',    't':'-',
                     'u':'..-',    'v':'...-',   'w':'.--',    'x':'-..-',
                     'y':'-.--',   'z':'--..',   '0':'-----',  '1':'.----',
                     '2':'..---',  '3':'...--',  '4':'....-',  '5':'.....',
                     '6':'-....',  '7':'--...',  '8':'---..',  '9':'----.',
                     '.':'.-.-.-', ',':'--..--', '?':'..--..', "'":'.----.',
                     '!':'-.-.--', '/':'-..-.',  '(':'-.--.',  ')':'-.--.-',
                     '&':'.-...',  ':':'---...', ';':'-.-.-.', '=':'-...-',
                     '+':'.-.-.',  '-':'-....-', '_':'..--.-', '"':'.-..-.',
                     '$':'...-..-','@':'.--.-.'}
                    

    def __init__(self, frequency=750.0, speed=25.0, eff_speed=15.0, volume=0):
        self.__logger = logging.getLogger("kochmorse")
        self.__logger.info("Init morse with speed %s(%s), volume %s and freq %s"%(speed, eff_speed, volume, frequency))
        assert volume <= 0
        
        # call constructor
        Thread.__init__(self)

        # store attr:
        self.__frequency = float(frequency)
        self.__speed     = float(speed)
        self.__eff_speed = float(eff_speed)
        self.__volume    = 10.0**(float(volume)/20.0)

        self.__dit_sample = str()
        self.__da_sample  = str()
        self.__char_pause = str()
        self.__word_pause = str()

        # open alsa-pcm device for output:        
        self.__pcm = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK)

        self.__sample_rate = 16000.0

        self.__pcm.setchannels(1)
        self.__pcm.setrate(int(self.__sample_rate))
        self.__pcm.setformat(alsaaudio.PCM_FORMAT_S16_LE)
     
        self.__sample_lock = Lock()
        self.__generate_samples()
 
        # thread stuff
        self.__running = True
        self.__pause = Event()

        self.__char_source = None
        self.__char_source_lock = Lock()
 
        self.__char_notifier = None
 
        # start thread
        self.start()
 

    def __generate_samples(self): 
        self.__sample_lock.acquire()
        # calculate sample-lengths: 
        self.__dit_len        = (60.0 * self.__sample_rate) / (50.0 * float(self.__speed))
        self.__dit_len        = ceil(self.__dit_len*self.__frequency/self.__sample_rate)*(self.__sample_rate/self.__frequency)
        self.__char_pause_len = (240 * self.__sample_rate) / (50.0 * float(self.__eff_speed))
        self.__word_pause_len = (360.0 * self.__sample_rate) / (50.0 * float(self.__eff_speed))
        self.__buffer_len     = int(ceil(self.__dit_len/4.0))

        # round lengths to multible of buffer length
        self.__dit_len        = int(round(self.__dit_len/self.__buffer_len))
        self.__char_pause_len = int(round(self.__char_pause_len/self.__buffer_len))
        self.__word_pause_len = int(round(self.__word_pause_len/self.__buffer_len))

        # calculate dit-sample:
        self.__dit_sample = str()
        for i in range(self.__dit_len*self.__buffer_len):  # calc sine tone
            self.__dit_sample += struct.pack("<h", self.__volume *32767.0 * sin(2.0*pi * self.__frequency * float(i)/self.__sample_rate) )
        for i in range(self.__dit_len*self.__buffer_len):  # add dit-pause
            self.__dit_sample += struct.pack("<h", 0.0)

        #calculate da-sample:
        self.__da_sample = str()
        for i in range( 3*self.__dit_len*self.__buffer_len):
            self.__da_sample += struct.pack("<h", self.__volume * 32767.0 * sin(2.0*pi * self.__frequency * float(i)/self.__sample_rate) )
        for i in range(self.__dit_len*self.__buffer_len):  # add dit-pause
            self.__da_sample += struct.pack("<h", 0.0)

        # "calculate" pause-sample:
        self.__pause_sample = str()
        for i in range(self.__buffer_len):
            self.__pause_sample += struct.pack("<h", 0.0)
        
        # set buffer size:
        self.__pcm.setperiodsize(self.__buffer_len)
        self.__sample_lock.release()


    def run(self):
        # main thread-loop
        while self.__running:        
            self.__pause.wait()
            if not self.__running: return
            
            # if there is no ChararcterSource -> pause; continue     
            if not (hasattr(self.__char_source, "__iter__") or isinstance(self.__char_source, basestring)):
                self.__pause.clear()
                continue
   
            # lock source and get char from source
            self.__char_source_lock.acquire()

            for char in self.__char_source:
                # if " " send a word-pause
                if char == " ":
                    self._send_word_pause()

                # if char is in table -> send each symbol
                elif char in self.__morse_table.keys():
                    for sym in self.__morse_table[char]:
                        if sym == ".": 
                            self._send_dit()
                        elif sym == "-": 
                            self._send_da()
                    self._send_char_pause()
               
                # if char is not known
                else: 
                    self.__logger.warning("Unknown char '%s'"%char);  
 
                if hasattr(self.__char_notifier, "__call__"):
                    self.__char_notifier(char)

                if not self.__pause.isSet() or not self.__running:
                    break 

            # done, unlock source
            self.__char_source_lock.release()

            # finnaly go into pause
            self.__pause.clear()  


    def set_char_source(self, src):
        # save old pause-state
        is_set = self.__pause.isSet()
        # set thread into pause:
        self.__pause.clear()

        # reset char source
        self.__char_source_lock.acquire()
        self.__char_source = src
        self.__char_source_lock.release()

        #reset state    
        if is_set: self.__pause.set()                          


    def stop(self):
        self.__running = False
        self.__pause.set()
        self.join()

    def is_running(self):
        return self.__running

    
    def is_paused(self):
        return not self.__pause.isSet()


    def set_pause(self, paused=True):
        if paused:
            self.__pause.clear()
        else:
            self.__pause.set()
        time.sleep(0.01)

    def set_notifier(self, notifier):
        self.__char_notifier = notifier


    def set_volume(self, volume):
        assert volume <= 0
        self.__volume = 10.0**(float(volume)/20.0)
        self.__generate_samples()

    def set_speed(self, speed):
        self.__speed = speed
        self.__generate_samples()

    def set_eff_speed(self, speed):
        self.__eff_speed = speed
        self.__generate_samples()

    def set_frequency(self, freq):
        self.__frequency = freq
        self.__generate_samples()
       
 
    def _send_dit(self):
        for i in range(2*self.__dit_len):
            self.__sample_lock.acquire()
            l =self.__pcm.write(self.__dit_sample[i*self.__buffer_len*2:(i+1)*self.__buffer_len*2])
            self.__sample_lock.release()
            time.sleep(0.00001)
            if not l == self.__buffer_len:
                self.__logger.warning("Buffer err: %i <> %i"%(l, self.__buffer_len))

    def _send_da(self):
        for i in range(4*self.__dit_len):
            self.__sample_lock.acquire()
            l = self.__pcm.write(self.__da_sample[i*self.__buffer_len*2:(i+1)*self.__buffer_len*2])
            self.__sample_lock.release()
            time.sleep(0.00001)
            if not l == self.__buffer_len:
                self.__logger.warning("Buffer err: %i <> %i"%(l, self.__buffer_len))

    def _send_char_pause(self):
        for i in range(self.__char_pause_len):
            self.__sample_lock.acquire()
            l = self.__pcm.write(self.__pause_sample)
            self.__sample_lock.release()
            time.sleep(0.00001)
            if not l == self.__buffer_len:
                self.__logger.warning("Buffer err: %i <> %i"%(l, self.__buffer_len))

    def _send_word_pause(self):
        for i in range(self.__word_pause_len):
            self.__sample_lock.acquire()
            l = self.__pcm.write(self.__pause_sample)
            self.__sample_lock.release()
            time.sleep(0.00001)
            if not l == self.__buffer_len:
                self.__logger.warning("Buffer err: %i <> %i"%(l, self.__buffer_len))
